﻿/**
 * @author bruno perry
 * @since 16-11-2009
 * 
 * Small flash 3d render engine.
 * Used to perform 3d transformations to graphic data
 * Transformations order:
 * 	WORLD -> SCREEN
 * 
 * This is an excerpt from the MICRO-GZM project (code.google.com/p/micro-gzm/)
 */ 
/** 
 *@TODO OVERALL
 *	-Add face culling (DONE 171109)
 * 	-Add color attributes
 * 	-Add alpha attributes 
 * 	-Add light support
 * 	-Add illustrator support (plugin)
 */
import math3D.Matrix3D;
import math3D.Vector3D;

import mcs.AlertIcon3D;

class MovieClip3D extends MovieClip {
	
	//shape data
	private var vertsData_array:Array;
	private var verts_array:Array;
	
	//refernece to cull (triangle)
	private var triangle:Object;
	
	//transformations data
	private var worldMat:Matrix3D;
	private var modelMat:Matrix3D;
	private var transfMat:Matrix3D;
	private var transfVerts_array:Array;
	
	//transformation value
	private var ticker:Number = 0;
	
	//cull on/off
	private var cull:Boolean;
	
	//pixels
	private var canvas:MovieClip;
	
	//transformation values
	private var POS:Vector3D;
	private var ROT:Vector3D;
	
	//light
	private var light:Object;
	
	//constants
	private static var SPEED:Number = -6;
	private static var PERSP:Number = 1.2;
	private static var FOV:Number = 118;
	private static var BACKFACE_CULLING:Boolean = true;
	private static var LIGHTING:Boolean = false;
	
	//properties
	private var faceColor:Number = 0xECECC9;
//	private var faceColor:Number = 0xFFFFFF;
	private var edgeColor:Number = 0x787864;
	private var faceAlpha:Number = 100;
	private var edgeAlpha:Number = 100;
	
	public function MovieClip3D() {
		/**
		 * @TODO ILLUSTRATOR PLUG IN
		 * 		finish the micro-gzm-exporter plugin for Illustrator
		 */
		vertsData_array = new Array(["m", -1.42, 9.69, -32.0],
	["l", -1.42, -12.29, -32.0],
	["c" ,-1.42, -14.96, -32.0, -4.06, -14.96, -32.0], ["c",-8.34, -14.96, -32.0 , -14.35, -2.42, -32.0],
	["c",-17.05, 3.6, -32.0, -16.69, 9.69 ,-32.0],["c",-16.69, 12.36, -32.0, -14, 12.36 ,-32.0],
	["l", -4.11, 12.36, -32.0],["c",-1.42, 12.36, -32.0, -1.42, 9.69 ,-32.0],
	["m", 1.39, -12.29, -32.0],
	["l", 1.39, -0.02, -32.0],["c",1.39, 1.25, -32.0, 2.46, 2.03, -32.0],
	["c",6.56, 5.48, -32.0, 7.52, 10.43, -32.0],["c",7.93, 12.24, -32.0, 9.97, 12.36, -32.0],
	["l", 13.96,12.36, -32.0],["c",16.69, 12.36, -32.0, 16.65,9.69, -32.0],
	["c", 16.7, 3.65, -32.0, 14.31, -2.42, -32.0],["c", 8.72, -14.96, -32.0, 4.02, -14.96, -32.0],
	["c", 1.39, -14.96, -32.0, 1.39, -12.29, -32.0]);
	}
	/**
	 * GET/SET vertices
	 * @param v new vertices list
	 * @return vertices list
	 */
	public function set VERTS( v:Array ):Void { 
		verts_array = v;
		defineShape();
	}
	public function get VERTS():Array {
		return verts_array;
	}
	/**
	 * GET/SET matrix
	 * @param m new transformation matrix
	 * @return transformation matrix
	 */
	public function set MATRIX( m:Matrix3D ):Void { 
		transfMat = m;
		render();
	}
	public function get MATRIX():Matrix3D {
		return transfMat;
	}
	/**
	 * Initializes the movie clip
	 */
	public function init():Void {
		
		worldMat = new Matrix3D();
		transfMat = new Matrix3D();
		modelMat = new Matrix3D();
		
		canvas = this.createEmptyMovieClip("canvas", this.getNextHighestDepth());
		defineShape();
		
		triangle = createTriangle();
		
		light = createLight();
		
		POS = new Vector3D();
		POS.x = 0.0; POS.y = 0.0; POS.z = 142;
		ROT = new Vector3D();
		ROT.x = 0; ROT.y = 0; ROT.z = 0;
		
		this.onEnterFrame = render;
		this.onRollOver = mouseOver;
		this.onRollOut = mouseOut;
	}
	/**
	 * Render the scene (perform world transformations and apply projection to 
	 * 					 movie clip)
	 */
	private function render():Void {
		
		transform3D();
		
		/**
		 * @TODO OK FOR CULLING,
		 * but handle a better way of lighting...
		 */
		triangle.a = transfVerts_array[0];
		triangle.b = transfVerts_array[3];
		triangle.c = transfVerts_array[6];
		
		//check for culling
		if(BACKFACE_CULLING) cull = !backfaceCull(triangle.a, 
												  triangle.b, 
												  triangle.c);
		if(LIGHTING) lightScene();
		//paint canvas
		if(!cull)draw();
		else canvas.clear();
		
		/**
		 * @TODO HANDLE VALUES DIFERENTLY
		 * :PPPPP (fix this)
		 * get anohter way to animate...
		 */
		//update tranform value
		ticker += SPEED;
	}
	/**
	 * Transforms the 3D  movie clip
	 */
	private function transform3D():Void {
		
		//transform (WORLD)
		worldMat.translate(POS);
		transfMat.Rotate(ticker, 0, 1, 0);
		
		var finalMat:Matrix3D = transfMat.multiply(worldMat);
		transfVerts_array = new Array();
		/**
		 *@TODO MULTIPLE ROTATIONS NOT WORKING!!
		 *      rotation matrices must be concatenated before applying the final matrix.
		 *      Also there is something wrong with the calculus in transfMat.rotate,
		 *      wrong  values are being used.
		 */
//		var finalMat:Matrix3D = transfMat.rotate( ticker, ROT );
//		finalMat.translate(POS);
		
		/**
		 * @TODO DONT USE ORIGINAL VERTICES!!
		 * 		get another list for the projection, this way we dont override original
		 * 		vertices (e.g var finalVerts_array:Array = verts_array)
		 * 
		 */
	 	//transform (OBJECT -> WORLD)
		var i:Number;
		for( i = 0; i < verts_array.length; i++ ) {
			//transform, and project vertices
			transfVerts_array.push( project(finalMat.Vector3DMatrix3DMultiply(verts_array[i])) );
			transfVerts_array[i].type = verts_array[i].type;	
		}
	}
	/**
	 * Converts 3D coordinates to 2D screen coordinates
	 * @param vec Vector in WORLD space to project into 2D - SCREEN SPACE
	 * @return projected vector (2d)
	 */
	public function project( vec:Vector3D ):Vector3D {
		
		//set perspective
		var invz:Number = (PERSP / vec.z);
		
		// projected vertices (SCREEN SPACE 2D)
		var sx:Number = (vec.x * FOV) * invz;
		var sy:Number = (vec.y * FOV) * invz;
		
		//2d vector(projected)
		return new Vector3D(sx, sy, 0);
	}
	/**
	 * Draw the transformed shape
	 */
	private function draw():Void {
		
		//clear all pixels in canvas
		canvas.clear();
		//setup color and alpha
		/*
		 * @TODO Color and alpha values shoud be dynamic
		 */
		canvas.beginFill(faceColor, 100);
		var i:Number;
		for( i = 0; i < transfVerts_array.length; i++ ){
			
			//move to
			if(transfVerts_array[i].type == "m") canvas.moveTo(transfVerts_array[i].x, transfVerts_array[i].y);
			
			//line to
			if(transfVerts_array[i].type == "l") canvas.lineTo(transfVerts_array[i].x, transfVerts_array[i].y);
			
			//curve to
			if(transfVerts_array[i].type == "c") {
				canvas.curveTo(transfVerts_array[i].x,   transfVerts_array[i].y, 
				           	   transfVerts_array[i+1].x, transfVerts_array[i+1].y);
				i++;
			}
		}
		canvas.endFill();
	}	
	/**
	 * Backface culling
	 */
	public function backfaceCull(p1:Vector3D,p2:Vector3D,p3:Vector3D):Boolean {
		return ((p2.x-p1.x)*(p3.y-p1.y)<(p3.x-p1.x)*(p2.y-p1.y));
	}
	
	private function lightScene():Void {
	
		var angle:Number;
		angle = light.pos.Dot(triangle.a);
	}
	/**
	 * Setup the shape
	 *
	 * -at this moment im converting flash vector art to accomadate
	 * 	the render engine. 
	 * 	another approach could be used.
	 */
	private function defineShape():Void {
		
		verts_array = new Array();
		
		var i:Number;
		for( i = 0; i < vertsData_array.length; i++ ){
			
			if(vertsData_array[i].length > 4){
				
				//curve coord
				 verts_array.push(new Vector3D( vertsData_array[i][1],
											    vertsData_array[i][2],
											    vertsData_array[i][3],
											    vertsData_array[i][0]),
											    
								  new Vector3D( vertsData_array[i][4],
											    vertsData_array[i][5],
											    vertsData_array[i][6],
											    vertsData_array[i][0]) );
			}						  
			else {
				//point coord
				verts_array.push(new Vector3D( vertsData_array[i][1],
										       vertsData_array[i][2],
										       vertsData_array[i][3],
										       vertsData_array[i][0]) );
			}
		}
	}
	
	private function createLight():Object {
		
		var p:Vector3D = new Vector3D();
		p.x = 0.0; p.y = 0.0; p.z = 10.0;
		
		var l:Object = new Object();
		l.pos = p;
		
		l.color = 0xffffff;
		return l;
	}
	/**
	 * Creates a triangle,
	 *  used as reference for the backface culling and lighting
	 */
	private function createTriangle():Object {
		
		var t:Object = new Object();
		t = {  a:Vector3D, b:Vector3D, c:Vector3D };
		return t;
	}
	
	/**************************************************************************
	 * EVENT HANDLERS
	 *************************************************************************/
	private function mouseOver():Void {
		
		delete this.onEnterFrame;
		
		this.onMouseDown = mouseDown;
		
		POS.z = 150;
		ticker = 0;
		transform3D();
		draw();
	}
	private function mouseOut():Void {
		
		delete this.onMouseDown;
		delete this.onMouseUp;
		
		POS.z = 142;
		this.onEnterFrame = render;
	}
	private function mouseDown():Void {
		
		this.onMouseUp = mouseUp;
		
		POS.z = 180;
		transform3D();
		draw();
	}
	private function mouseUp():Void {
		
		POS.z = 150;
		transform3D();
		draw();
	}
}
